#include "../../includes/network/cudpsocket.h"
#include "../../includes/QsLog/QsLog.h"
#include "../../includes/common/common.h"

#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <ctype.h>
#include <string.h>
#include <qDebug>

static int s_KCP_ID = 0x223344;

int KCP_Networker_Callback(const char * buf, int len, ikcpcb * kcp, void * user)
{
    auto kcpSocket = (CUdpSocket*)user;
    return kcpSocket->onProcessSendData(buf, len);
}

/**
 * @brief CUdpSocket::CUdpSocket
 * @param parent
 * @param isEnableKcp  是否启用kcp，默认是启用
 * @param isEnableReciver 是否接收数据，默认是启用
 * @param receivePort 接收数据的端口，如果设置成-1表示产生一个随机端口,否则启动指定的端口,
 *        随机端口号在1000-2000之间
 */
CUdpSocket::CUdpSocket(QObject *parent,bool isEnableKcp,bool isEnableReciver,int receivePort)
    : m_isProcessDatagramHearder(false),
      m_ikcp(NULL),
      m_address(QHostAddress::Broadcast),
      m_port(-1),
      m_enableKcp(isEnableKcp),
      m_msgHeartCount(0),
      m_currentHeartTime(0),
      m_isEnableReciver(isEnableReciver),
      m_receivePort(receivePort),
      m_isSendHeart(true)
{
    if(m_enableKcp)
    {
        m_ikcp = ikcp_create(s_KCP_ID, this);

        switch (s_KCP_MODE)
        {
        case 0:
            ikcp_nodelay(m_ikcp, 0, 10, 0, 0);
            break;
        case 1:
            ikcp_nodelay(m_ikcp, 0, 10, 0, 1);
            break;
        case 2:
            ikcp_nodelay(m_ikcp, 1, 10, 2, 1);
            m_ikcp->rx_minrto = 10;
            m_ikcp->fastresend = 1;
            break;
        default:
            break;
        }

        m_ikcp->output = KCP_Networker_Callback;
    }

    if(m_isEnableReciver)
    {
        if(m_receivePort == -1)
        {
            qsrand(time(NULL));
            m_receivePort = qrand() % 1000 + 1000;
        }

        m_UdpSocket.bind(m_receivePort,QUdpSocket::ShareAddress);

        QLOG_INFO()<<"CUdpSocket bind port:"<<QString::asprintf("%d",m_receivePort);

        connect(&m_UdpSocket,&QUdpSocket::readyRead,this,&CUdpSocket::reciverPendingDatagram);
    }

    QObject::connect(&m_updateTimer, &QTimer::timeout,
        this, &CUdpSocket::KCPUpdate);
    m_updateTimer.start(10);
}

CUdpSocket::~CUdpSocket(void)
{
    if(m_enableKcp)
    {
        ikcp_flush(m_ikcp);
        ikcp_release(m_ikcp);
    }
}

/**
 * @brief CUdpSocket::connect 设置要连接的地址和端口
 * @param address 要设置的电脑IP地址
 * @param port 电脑端口
 */
void CUdpSocket::Connect(QHostAddress address,int port)
{
    m_address = address;
    m_port = port;
}

/**
 * @brief CUdpSocket::getName 得到socket的名称
 * @return
 */
QString CUdpSocket::getName(void)
{
    return m_address.toString() + "_" + QString::asprintf("%ld",m_port);
}

/**
 * @brief CUdpSocket::Send 发送报文
 * @param buf 要发送的报文数据
 * @param len 要发送的报文数据长度
 * @return 返回发送成功的数据长度
 */
qint64 CUdpSocket::onProcessSendData(const char * buf, int len)
{
    if(buf == NULL || len <= 0)
        return -1;

    return m_UdpSocket.writeDatagram(buf,len,m_address,m_port);
}

/**
 * @brief CUdpSocket::Send 发送报文
 * @param Datagramdata 要发送的报文数据
 * @return 返回发送成功的数据长度
 */
qint64 CUdpSocket::SendByte(QByteArray Datagramdata)
{
    if(Datagramdata.isEmpty() || m_port <= 0)
        return -1;

    QByteArray pSendDatagramData;

    tagDatagramHearder ptagDatagramHearder;
    ptagDatagramHearder.version = 100;
    ptagDatagramHearder.size = Datagramdata.size();

    pSendDatagramData.append((char*)&ptagDatagramHearder,sizeof(tagDatagramHearder));
    pSendDatagramData.append(Datagramdata);

    return m_UdpSocket.writeDatagram(Datagramdata.data(),Datagramdata.size(),m_address,m_port);
}

/**
 * @brief CUdpSocket::Send 发送报文
 * @param buf 要发送的报文数据
 * @param len 要发送的报文数据长度
 * @return 返回发送成功的数据长度
 */
int CUdpSocket::SendKcpData(QByteArray Datagramdata)
{
    if(Datagramdata.isEmpty() ||
            m_enableKcp == false ||
            m_ikcp == NULL ||
            m_port <= 0)
        return -1;

    if (m_ikcp->nsnd_que>s_max_send_que)
    {
        return -1;
    }

    QByteArray pSendDatagramData;

    tagDatagramHearder ptagDatagramHearder;
    ptagDatagramHearder.version = 100;
    ptagDatagramHearder.size = Datagramdata.size();

    pSendDatagramData.append((char*)&ptagDatagramHearder,sizeof(tagDatagramHearder));
    pSendDatagramData.append(Datagramdata);

    return ikcp_send(m_ikcp,pSendDatagramData.data(),pSendDatagramData.size());
}

void CUdpSocket::KCPUpdate()
{
    if(m_ikcp == NULL || m_enableKcp == false)
        return;

    auto clock = iclock();
    ikcp_update(m_ikcp, clock);

    // 处理心跳(必须在用户设置了接收以后才能处理，否则会堆积大堆数据)
    if(m_isSendHeart && m_isEnableReciver && m_port > 0)
    {
        qint64 tempTime = (qint64)time(NULL);

        //qDebug()<<tempTime<<" "<<m_currentHeartTime<<" "<<tempTime-m_currentHeartTime;

        if(tempTime-m_currentHeartTime > 3)
        {
            m_currentHeartTime = tempTime;

            SendKcpData(QByteArray("100"));

            //qDebug()<<"SendKcpData:"<<m_currentHeartTime;
        }
    }
}

/**
 * @brief CUdpSocket::onProcessReciverDatagramdata 处理接收到的报文
 * @param Datagramdata 要处理的接收到的报文
 */
void CUdpSocket::onProcessReciverDatagramdata(void)
{
    while(!m_datagramData.isEmpty())
    {
        if(!m_isProcessDatagramHearder && m_datagramData.size() > sizeof(tagDatagramHearder))
        {
            memcpy(&m_DatagramHearder,m_datagramData.constData(),sizeof(tagDatagramHearder));

            if(m_DatagramHearder.version != IDD_UDP_VERSION)
            {
                QLOG_ERROR()<<"CUdpSocket::onProcessReciverDatagramdata:Version inconsistency";
                return;
            }

            m_datagramData.remove(0,sizeof(tagDatagramHearder));
            m_isProcessDatagramHearder = true;
        }

        if(!m_isProcessDatagramHearder || m_datagramData.size() < m_DatagramHearder.size)
            break;

        // 得到当前文件数据
        QByteArray precvDatagramData = m_datagramData.mid(0,m_DatagramHearder.size);
        m_datagramData.remove(0,m_DatagramHearder.size);

        m_msgHeartCount = 0;

        // 心跳消息不处理
        if(QString(precvDatagramData) != "100")
            emit processPendingDatagram(this,precvDatagramData);

        m_isProcessDatagramHearder=false;
    }
}

/**
 * @brief CUdpSocket::onProcessReciverKcpDatagramdata kcp处理接收到的报文
 * @param Datagramdata 要处理的报文
 * @param isenableKcp 是否是kcp报文
 */
void CUdpSocket::onProcessReciverKcpDatagramdata(QByteArray& Datagramdata,bool isenableKcp)
{
    if(Datagramdata.isEmpty())
        return;

    if(isenableKcp && m_ikcp)
    {
        ikcp_input(m_ikcp, Datagramdata.data(), Datagramdata.size());

        char buf[s_MTU];
        while (true)
        {
            int hr = ikcp_recv(m_ikcp, buf, s_MTU);
            if (hr<0)
            {
                break;
            }

            m_datagramData.append(buf, hr);
        }
    }
    else
    {
        m_datagramData.append(Datagramdata);
    }
}

void CUdpSocket::reciverPendingDatagram()
{
    while(m_UdpSocket.hasPendingDatagrams())
    {
        QByteArray preciverDatagramData;

        preciverDatagramData.resize(m_UdpSocket.pendingDatagramSize());

        if(m_UdpSocket.readDatagram(preciverDatagramData.data(),preciverDatagramData.size()) > 0)
        {
            onProcessReciverKcpDatagramdata(preciverDatagramData,m_enableKcp);
        }
    }

    onProcessReciverDatagramdata();
}

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////

CUdpServer::CUdpServer(QObject *parent,bool isEnableKcp)
    : m_enableKcp(isEnableKcp)
{
    connect(&m_UdpSocket,&QUdpSocket::readyRead,this,&CUdpServer::reciverPendingDatagram);

    QObject::connect(&m_updateTimer, &QTimer::timeout,
        this, &CUdpServer::KCPUpdate);
    m_updateTimer.start(1000);
}

CUdpServer::~CUdpServer(void)
{
    Clear();
}

/**
 * @brief CUdpServer::open 设置要打开的端口
 * @param port 要打开的端口
 * @return 如果端口绑定成功返回真，否则返回假
 */
bool CUdpServer::open(int port)
{
    if(port <= 0)
        return false;

    return m_UdpSocket.bind(QHostAddress::LocalHost,port);
}

/**
 * @brief CUdpServer::stop 停止服务器
 */
void CUdpServer::stop(void)
{
    m_UdpSocket.close();
}

/**
 * @brief CUdpServer::Clear 清除所有的数据
 */
void CUdpServer::Clear(void)
{
    QHash<QString,CUdpSocket*>::iterator iter = m_udpClients.begin();
    for(;iter != m_udpClients.end();++iter)
    {
        delete (*iter);
    }

    m_udpClients.clear();
    m_udpKcpClients.clear();
}

void CUdpServer::KCPUpdate()
{
    if(m_udpClients.isEmpty())
        return;

    QHash<QString,CUdpSocket*>::iterator iter = m_udpClients.begin();
    for(;iter != m_udpClients.end();)
    {
        CUdpSocket *pClient = (*iter);

        if(pClient->GetHeartCount() > 5)
        {
            QHash<QUdpSocket*,CUdpSocket*>::iterator iterTwo = m_udpKcpClients.find(pClient->getSocketID());
            if(iterTwo != m_udpKcpClients.end())
                m_udpKcpClients.erase(iterTwo);

            disconnect((*iter),SIGNAL(processPendingDatagram(CUdpSocket*,QByteArray)),this,SLOT(on_process_processPendingDatagram(CUdpSocket*,QByteArray)));

            emit signal_ClientLeave(pClient);

            delete pClient;
            iter = m_udpClients.erase(iter);
        }
        else
        {
            (*iter)->heart();
            ++iter;
        }
    }
}

/**
 * @brief CUdpServer::SendAll 发送消息给所有人
 * @param datagramdata 要发送的数据包
 */
void CUdpServer::SendAll(QByteArray datagramdata)
{
    if(m_udpKcpClients.isEmpty())
        return;

    QHash<QUdpSocket*,CUdpSocket*>::iterator iterTwo = m_udpKcpClients.begin();
    for(;iterTwo != m_udpKcpClients.end();++iterTwo)
    {
        if(m_enableKcp) (*iterTwo)->SendKcpData(datagramdata);
        else (*iterTwo)->SendByte(datagramdata);
    }
}

/// 发送除了指定socketid的其它人
void CUdpServer::SendOther(QUdpSocket* socketid,QByteArray datagramdata)
{
    if(m_udpKcpClients.isEmpty())
        return;

    QHash<QUdpSocket*,CUdpSocket*>::iterator iterTwo = m_udpKcpClients.begin();
    for(;iterTwo != m_udpKcpClients.end();++iterTwo)
    {
        if((*iterTwo)->getSocketID() == socketid)
            continue;

        if(m_enableKcp) (*iterTwo)->SendKcpData(datagramdata);
        else (*iterTwo)->SendByte(datagramdata);
    }
}

/// 发送给指定socketid
void CUdpServer::SendTo(QUdpSocket* socketid,QByteArray datagramdata)
{
    if(m_udpKcpClients.isEmpty())
        return;

    QHash<QUdpSocket*,CUdpSocket*>::iterator iterTwo = m_udpKcpClients.find(socketid);
    if(iterTwo != m_udpKcpClients.end())
    {
        if(m_enableKcp) (*iterTwo)->SendKcpData(datagramdata);
        else (*iterTwo)->SendByte(datagramdata);
    }
}

void CUdpServer::on_process_processPendingDatagram(CUdpSocket* socketid,QByteArray datagramdata)
{
    emit signal_ClientReceiveMsg(socketid,datagramdata);
}

void CUdpServer::reciverPendingDatagram()
{
    while(m_UdpSocket.hasPendingDatagrams())
    {
        QByteArray preciverDatagramData;
        QHostAddress sender;
        quint16 senderPort = 0;

        preciverDatagramData.resize(m_UdpSocket.pendingDatagramSize());

        if(m_UdpSocket.readDatagram(preciverDatagramData.data(),preciverDatagramData.size(),
                                    &sender, &senderPort) > 0)
        {
            QString kcpName = sender.toString() + "_" + QString::asprintf("%ld",senderPort);
            CUdpSocket *pUdpSocket = NULL;

            QHash<QString,CUdpSocket*>::iterator iter = m_udpClients.find(kcpName);
            if(iter == m_udpClients.end())
            {
                pUdpSocket = new CUdpSocket(nullptr,true,false);
                pUdpSocket->Connect(sender,senderPort);

                connect(pUdpSocket,SIGNAL(processPendingDatagram(CUdpSocket*,QByteArray)),this,SLOT(on_process_processPendingDatagram(CUdpSocket*,QByteArray)));

                m_udpClients[kcpName] = pUdpSocket;
                m_udpKcpClients[pUdpSocket->getSocketID()] = pUdpSocket;

                emit signal_ClientArrive(pUdpSocket);
            }
            else
            {
                pUdpSocket = (*iter);
            }

            pUdpSocket->onProcessReciverKcpDatagramdata(preciverDatagramData,m_enableKcp);

            // 这里和CUdpSocket处理不太一样，这里是处理多个客户端发送的消息，如果放在外面处理可能会存在多个客户端的
            // 消息，如果放在外面处理可能会导致只处理一个客户端的消息，所以需要在每个客户端收到消息后就自行处理自己客户端的
            // 消息.
            pUdpSocket->onProcessReciverDatagramdata();
        }
    }
}

/**
 * @brief CUdpServer::getSocket 根据socketid得到相应的socket
 * @param socketid socket的id
 * @return
 */
CUdpSocket* CUdpServer::getSocket(QUdpSocket *socketid)
{
    if(m_udpKcpClients.isEmpty() || socketid == NULL)
        return NULL;

    QHash<QUdpSocket*,CUdpSocket*>::iterator iter = m_udpKcpClients.find(socketid);
    if(iter != m_udpKcpClients.end())
        return (*iter);

    return NULL;
}

/**
 * @brief CUdpServer::addSocket 添加一个新的客户端
 * @param address 要添加的客户端的IP
 * @param port 要添加的客户端的端口
 *
 * @return 返回新添加的客户端
 */
CUdpSocket* CUdpServer::addSocket(QHostAddress address,int port)
{
    QString kcpName = address.toString() + "_" + QString::asprintf("%d",port);
    CUdpSocket *pUdpSocket = NULL;

    QHash<QString,CUdpSocket*>::iterator iter = m_udpClients.find(kcpName);
    if(iter == m_udpClients.end())
    {
        pUdpSocket = new CUdpSocket(nullptr,true,false);
        pUdpSocket->Connect(address,port);

        connect(pUdpSocket,SIGNAL(processPendingDatagram(CUdpSocket*,QByteArray)),this,SLOT(on_process_processPendingDatagram(CUdpSocket*,QByteArray)));

        m_udpClients[kcpName] = pUdpSocket;
        m_udpKcpClients[pUdpSocket->getSocketID()] = pUdpSocket;
    }
    else
    {
        pUdpSocket = (*iter);
    }

    return pUdpSocket;
}

/**
 * @brief CUdpServer::getSocketByName 根据名称得到客户端
 * @param name 客户端的名称
 * @return 如果客户端存在返回这个客户端，否则返回NULL
 */
CUdpSocket* CUdpServer::getSocketByName(QString name)
{
    if(name.isEmpty())
        return NULL;

    QHash<QString,CUdpSocket*>::iterator iter = m_udpClients.find(name);
    if(iter != m_udpClients.end())
        return (*iter);

    return NULL;
}